home *** CD-ROM | disk | FTP | other *** search
/ SGI Freeware 1998 June / SGI Freeware 1998 June.iso / dist / fw_ATxgopher.idb / usr / freeware / src / xgopher.1.3 / util.c.z / util.c
C/C++ Source or Header  |  1998-01-21  |  24KB  |  1,131 lines

  1. /* util.c
  2.    Utility routines to maintain and process gopher data structures
  3.    and events. */
  4.  
  5.      /*---------------------------------------------------------------*/
  6.      /* Xgopher        version 1.3     08 April 1993                  */
  7.      /*                version 1.2     20 November 1992               */
  8.      /*                version 1.1     20 April 1992                  */
  9.      /*                version 1.0     04 March 1992                  */
  10.      /* X window system client for the University of Minnesota        */
  11.      /*                                Internet Gopher System.        */
  12.      /* Allan Tuchman, University of Illinois at Urbana-Champaign     */
  13.      /*                Computing and Communications Services Office   */
  14.      /* Copyright 1992, 1993 by                                       */
  15.      /*           the Board of Trustees of the University of Illinois */
  16.      /* Permission is granted to freely copy and redistribute this    */
  17.      /* software with the copyright notice intact.                    */
  18.      /*---------------------------------------------------------------*/
  19.  
  20. /* ***** really, this file needs to divide into 3:
  21.         itemPublic.c itemUtil.c itemClass.c
  22.      there are some global variables to consider, though.
  23.    ***** */
  24.  
  25. #include <stdio.h>
  26.  
  27. #include "osdep.h"
  28. #include "conf.h"
  29. #include "gopher.h"
  30. #include "globals.h"
  31. #include "gui.h"
  32. #include "item.h"
  33. #include "itemList.h"
  34. #include "dir.h"
  35. #include "dirList.h"
  36. #include "util.h"
  37. #include "status.h"
  38. #include "misc.h"
  39. #include "net.h"
  40. #include "appres.h"
  41. #include "jobs.h"
  42.  
  43.  
  44. #include <ctype.h>
  45. #include <errno.h>
  46.  
  47. #include "sc_text.h"
  48. #include "sc_dir.h"
  49. #include "sc_cso.h"
  50. #include "sc_index.h"
  51. #include "sc_telnet.h"
  52. #include "sc_tn3270.h"
  53. #include "sc_binary.h"
  54. #include "sc_image.h"
  55. #include "sc_sound.h"
  56. #include "sc_extend.h"
  57.  
  58. #ifdef Mips
  59. #ifdef BSD43
  60. extern int    errno;
  61. #endif
  62. #endif    /* Mips */
  63.  
  64. char    prefixUnknown [ PREFIX_LEN ];
  65.  
  66. scInfo  noSubclass = {
  67.                 "unknown gopher item type",
  68.                 prefixUnknown,
  69.                 &nullHostList,
  70.                 GI_access,
  71.         GI_copyToFile,
  72.         copyTypeNone,
  73.         GI_process,
  74.         GI_init,
  75.         GI_done,
  76.         GI_restart
  77.         };
  78.  
  79. #define UNKNOWN_TYPE(gi)    ((gi)->sc == &noSubclass)
  80.  
  81.  
  82.  
  83. typedef struct giSubclassStruct {
  84.         char    typeID;
  85.     scInfo    *subclass;
  86.     struct giSubclassStruct *next;
  87.    } giSubclass;
  88.  
  89. /* the order of the following subclasses is important.  All directory
  90.    types must come first, then the extended types (so they can 
  91.    override internal ones), then all other built-in types. */
  92.  
  93. static giSubclass    definedSubclass[] = {
  94.         {A_DIRECTORY,        &dirSubclass,    NULL},
  95.         {A_INDEX,        &indexSubclass,    NULL},
  96.         {A_EXTENDED,        &extendSubclass,NULL},
  97.         {A_FILE,        &textSubclass,    NULL},
  98.         {A_CSO,            &csoSubclass,    NULL},
  99.         {A_BINARY,        &binarySubclass,NULL},
  100.         {A_IMAGE,        &imageSubclass,    NULL},
  101.         {A_TELNET,        &telnetSubclass,NULL},
  102.         {A_TN3270,        &tn3270Subclass,NULL},
  103.         {A_SOUND,        &soundSubclass,    NULL},
  104.         /* '?' type will be Unknown */
  105.         {'?',            &noSubclass,    NULL},
  106.         {'\0',            &noSubclass,    NULL},
  107.    };
  108.  
  109.         /* the following can be implemented as extended types: 
  110.  
  111.         A_MAC_BINHEX, (mac binhex),
  112.         A_DOS_BINHEX, (dos binhex),
  113.         A_UNIX_UUENCODE, (uuencoded file)
  114.     */
  115.  
  116. static    giSubclass    *activeSubclass = NULL;
  117.  
  118.  
  119. /* ====================================================================== */
  120. /*                                                                        */
  121. /* publicly callable procedures                                           */
  122. /*                                                                        */
  123. /* ====================================================================== */
  124.  
  125. /* makeItem
  126.    Given the contents, allocate and build a new gopher item */
  127.  
  128. gopherItemP
  129. makeItem(t, titleString, selectString, host, port, plus)
  130. char    t;
  131. char    *titleString;
  132. char    *selectString;
  133. char    *host;
  134. int    port;
  135. BOOLEAN    plus;
  136. {
  137.     gopherItemP    gi = newItem();
  138.     giSubclass     *scID;
  139.  
  140.     gi->type = t;
  141.     strncpy(USER_STRING(gi), titleString, USER_STRING_LEN);
  142.     vStringSet(&(gi->selector), selectString);
  143.     strncpy(gi->host, host, HOST_STRING_LEN);
  144.     gi->port = port;
  145.     gi->plus = FALSE;
  146.  
  147.     /* determine subclass and set fields determined by those class
  148.        variables and class methods. */
  149.  
  150.     for (scID = activeSubclass;
  151.          scID->typeID != t && scID->typeID != A_UNKNOWN;
  152.          scID=scID->next) ;
  153.     gi->sc = scID->subclass;
  154.  
  155.     PREFIX(gi, gi->sc->typePrefix);
  156.  
  157.     gi->accessOk = gi->sc->checkAccess(gi);
  158.  
  159.     return gi;
  160. }
  161.  
  162.  
  163. /* initItemClasses
  164.    initialize each item class - gather its resources, restrictions,
  165.    and if appropriate, create its user interface resources. */
  166.  
  167. void
  168. initItemClasses()
  169. {
  170.     giSubclass    *scID;
  171.  
  172.     for (scID = definedSubclass; scID->typeID != '\0'; scID++) {
  173.         scID->subclass->initProc();
  174.     }
  175.  
  176. #ifdef DEBUG
  177.     fprintf (stderr, "Known item types are:\n");
  178.     for (scID = activeSubclass; scID != NULL; scID=scID->next) {
  179.         fprintf (stderr, "\t%c, %.5s, %s\n",
  180.             scID->typeID, scID->subclass->typePrefix,
  181.             scID->subclass->typeName);
  182.     }
  183. #endif    /* DEBUG */
  184.  
  185.     return;
  186. }
  187.  
  188.  
  189. /* doneItemClasses
  190.    terminate each item class - free its resources and user interface
  191.    resources as necessary. */
  192.  
  193. void
  194. doneItemClasses()
  195. {
  196.     giSubclass *scID;
  197.  
  198.     for (scID = activeSubclass; scID != NULL; scID=scID->next) {
  199.         scID->subclass->doneProc();
  200.     }
  201.  
  202.     killAllItemProcesses();
  203.  
  204.     return;
  205. }
  206.  
  207.  
  208. /* restartItemClasses
  209.    restart each item class - re-initialize its resources and user
  210.    interface components as necessar. */
  211.  
  212. void
  213. restartItemClasses()
  214. {
  215.     giSubclass *scID;
  216.  
  217.     for (scID = activeSubclass; scID != NULL; scID=scID->next) {
  218.         scID->subclass->restartProc();
  219.     }
  220.  
  221.     return;
  222. }
  223.  
  224.  
  225. /* processItem
  226.    check accessability, then process the selected item */
  227.  
  228. BOOLEAN
  229. processItem(gi)
  230. gopherItemP     gi;
  231. {
  232.     BOOLEAN     result=FALSE;
  233.  
  234.     if (! gi->accessOk) {
  235.         char message[MESSAGE_STRING_LEN];
  236.  
  237.         sprintf(message,
  238.             "Sorry, %s access is restricted in this session.",
  239.             gi->sc->typeName);
  240.         showError(message);
  241.         result = FALSE;
  242.     } else {
  243.         result = gi->sc->processItem(gi);;
  244.     }
  245.  
  246.     return result;
  247. }
  248.  
  249.  
  250. /* copyItemToFile
  251.    check accessability, then copy the selected item to a file */
  252.  
  253. BOOLEAN
  254. copyItemToFile(gi)
  255. gopherItemP     gi;
  256. {
  257.     BOOLEAN     result=FALSE;
  258.  
  259.     if (! gi->accessOk) {
  260.         char message[MESSAGE_STRING_LEN];
  261.  
  262.         sprintf(message,
  263.             "Sorry, %s access is restricted in this session.",
  264.             gi->sc->typeName);
  265.         showError(message);
  266.         result = FALSE;
  267.     } else {
  268.         result = gi->sc->copyProc(gi);;
  269.     }
  270.  
  271.     return result;
  272. }
  273.  
  274.  
  275. /* updateDirectory
  276.    reload an existing gopher directory (possibly an index search result) */
  277.  
  278. void
  279. updateDirectory(d)
  280. gopherDirP    d;
  281. {
  282.     int        s;
  283.     gopherItemP    gi = d->selectorItem;
  284.     BOOLEAN        fetchOK;
  285.  
  286.  
  287.     if ( (s = GI_connectWithStatus(gi)) < 0) return;
  288.  
  289.     writeString(s, vStringValue(&(gi->selector)));
  290.     writeString(s, EOL_STRING);
  291.  
  292.     showStatus("Reloading requested directory", STAT_DIRECTORY,
  293.              gi->host, gi->port);
  294.  
  295.     fetchOK = GI_getGopherDir(s, d);
  296.  
  297.     close(s);
  298.  
  299.     if (! removeStatusPanel() ) {
  300.         
  301.         /* someone cancelled as the directory load finished */
  302.  
  303.         return;
  304.  
  305.     }
  306.  
  307.     if (! fetchOK) {
  308.         /* failure to re-load the directory */
  309.         showError(
  310.             "There seems to be no information in this directory\n");
  311.         return;
  312.     }
  313.  
  314.     setDirTime(d);
  315.  
  316.     return;
  317. }
  318.  
  319.  
  320. /* ====================================================================== */
  321. /*                                                                        */
  322. /* Utilities used by the item class and its subclasses                    */
  323. /*                                                                        */
  324. /* ====================================================================== */
  325.  
  326.  
  327. /* GU_ftpCheck
  328.    return True (ok) if ftp is allowed or this item doesn't represent
  329.    an ftp request.  Return false if this this is an ftp request and 
  330.    ftp's are not allowed. */
  331.  
  332. static BOOLEAN
  333. GU_ftpCheck(gi)
  334. gopherItemP    gi;
  335. {
  336.     BOOLEAN    isFtp = FALSE;
  337.  
  338.     if ( appResources->allowFtp ) return TRUE;
  339.  
  340.     isFtp = (strncmp(vStringValue(&(gi->selector)), "ftp:", 4) == 0);
  341.  
  342.     /* don't see an "ftp:", but see if there's an '@' in the path */
  343.     if (! isFtp) {
  344.         isFtp = (index(vStringValue(&(gi->selector)), '@') != NULL);
  345.     }
  346.  
  347.     return ( ! isFtp );
  348. }
  349.  
  350.  
  351. /* GU_makePrefix
  352.    edit application resources of directory listing prefixes to conform
  353.    to be blank padded and exactly PREFIX_LEN characters.
  354.    NOTE: These strings are *NOT* NULL-terminated. */
  355.  
  356. void
  357. GU_makePrefix(target, source)
  358. char    *target, *source;
  359. {
  360.     int    i;
  361.  
  362.     for (i=0; i<PREFIX_LEN; i++) {
  363.         if (*source == NULLC) {
  364.             *(target++) = ' ';
  365.         } else {
  366.             *(target++) = *(source++);
  367.         }
  368.     }
  369. }
  370.  
  371.  
  372. /* GU_getGopherItem
  373.    receive a response from a gopher server, parsing it as it comes in.
  374.    Return the item pointer for success, NULL for failure. */
  375.  
  376. static gopherItemP
  377. GU_getGopherItem(gfd)
  378. int    gfd;
  379. {
  380.     char            buffer[255];
  381.     char    gType,
  382.         gName[USER_STRING_LEN],
  383.         gPath[SELECTOR_STRING_MAX_LEN],
  384.         gHost[HOST_STRING_LEN];
  385.     int    gPort;
  386.     BOOLEAN    gPlus;
  387.  
  388.  
  389.     if (readn(gfd, &(buffer[0]), 1) <= 0) {
  390.         /* immediate EOF or error -- no item read */
  391.         return NULL;
  392.     }
  393.  
  394.     /** Get the kind of file from the first character **/
  395.  
  396.     gType = buffer[0];
  397.  
  398.     if (gType == A_EOI) return NULL;
  399.  
  400.     if (readField(gfd, &(buffer[1]), 255) <= 0) {
  401.         /* immediate EOF or error -- no item read */
  402.         return NULL;
  403.     }
  404.  
  405.     /* get the User Displayable name */
  406.  
  407.     strncpy(gName, buffer + 1, USER_STRING_LEN);
  408.  
  409.     /* terminate the string just in case it was too long */
  410.  
  411.     gName[USER_STRING_LEN - 1] = '\0';
  412.  
  413.     /* get the Pathname */
  414.  
  415.     if (readField(gfd, gPath, SELECTOR_STRING_MAX_LEN) <= 0) {
  416.         /* EOF or error after TYPE/NAME  --
  417.            no complete item read */
  418.         return NULL;
  419.     }
  420.  
  421.     /* get the hostname */
  422.  
  423.     if (readField(gfd, gHost, HOST_STRING_LEN) == 0) {
  424.         /* EOF or error after TYPE/NAME/PATH  --
  425.            no complete item read */
  426.         return NULL;
  427.     }
  428.  
  429.     /* get the port number */
  430.  
  431.     if (readLine(gfd, buffer, 255)<=0) {
  432.         /* EOF or error after TYPE/NAME/PATH  --
  433.            Problem - should be a port number here.  But this
  434.            isn't as serious as above.  We'll try to proceed. */
  435.         ;
  436.     }
  437.  
  438.     gPort = 0;
  439.  
  440.     /* Get the port number */
  441.  
  442.     gPort = atoi(buffer);
  443.  
  444.     gPlus = False;
  445.  
  446.     return makeItem(gType, gName, gPath, gHost, gPort, gPlus);
  447. }
  448.  
  449.  
  450. /* GU_copySubclassRecord
  451.    return a copy of the class record for subclass identified.
  452.    Search both knownTypes, and types already initialized.  */
  453.  
  454. scInfo    *
  455. GU_copySubclassRecord(t)
  456. char    t;
  457. {
  458.     scInfo        *scRec;
  459.     giSubclass     *scID;
  460.  
  461.     scRec = (scInfo *) malloc(sizeof(scInfo));
  462.  
  463.     /* first check initialized types.  This allows an alias
  464.        to preferentially use a new external type. */
  465.  
  466.     for (scID = activeSubclass;
  467.             scID != NULL && scID->typeID != t; scID=scID->next) ;
  468.  
  469.     if (scID == NULL) {
  470.         /* it will be in this list or the final item type, unknown
  471.            will be used */
  472.         for (scID = definedSubclass;
  473.              scID->typeID != t && scID->typeID != '\0'; scID++) ;
  474.     }
  475.  
  476.     bcopy((char *) scID->subclass, (char *) scRec, sizeof(scInfo));
  477.  
  478.     return scRec;
  479. }
  480.  
  481.  
  482. /* GU_registerNewType
  483.    Add a new type identifier and its subclass record to the known list. */
  484.  
  485. void
  486. GU_registerNewType(t, scRec)
  487. char    t;
  488. scInfo    *scRec;
  489. {
  490.     giSubclass    *newSC = (giSubclass *) malloc (sizeof (giSubclass));
  491.     giSubclass    *sc;
  492.  
  493.     newSC->typeID   = t;
  494.     newSC->subclass = scRec;
  495.     newSC->next     = (giSubclass *) NULL;
  496.  
  497.     /* add to the end of the list for expected behavior */
  498.  
  499.     if (activeSubclass == (giSubclass *) NULL) {
  500.         activeSubclass = newSC;
  501.     } else {
  502.         for (sc=activeSubclass; sc != NULL; sc=sc->next) {
  503.         if (sc->typeID == t) {    /* already is one */
  504.             break;
  505.         }
  506.  
  507.         if (sc->next == (giSubclass *) NULL) {
  508.             sc->next = newSC;
  509.             break;
  510.         }
  511.         }
  512.     }
  513. }
  514.  
  515.  
  516. /* GU_isVowel
  517.    return True if first character of a string is a vowel, false otherwise */
  518.  
  519. static    char    vowels[] = "aAeEiIoOuU";
  520.  
  521. BOOLEAN
  522. GU_isVowel(str)
  523. char    *str;
  524. {
  525.     char    *v;    
  526.     for (v=vowels; (*str != *v  &&  *v != NULLC); v++ );
  527.  
  528.     return (*v != NULLC);
  529. }
  530.  
  531. /* Ideas for the implementation of the access limiting mechanism
  532.    came from the University of Minnesota gopher server code.
  533.    In particular, the matching of IP addresses and host name
  534.    matching algorithm is from that code. */
  535.  
  536.  
  537. /* GU_addHostToAccessList
  538.    add the host name or address to an access list.
  539.    Determine if a given string is a name or IP address by
  540.    checking the first character to see if it is numeric. */
  541.  
  542. static void
  543. GU_addHostToAccessList(list, host)
  544. accessList    *list;
  545. char        *host;
  546. {
  547.     char        *temp;
  548.     accessList    addr;
  549.  
  550.     temp = (char *) malloc((strlen(host) + 1) * sizeof(char));
  551.     strcpy(temp, host);
  552.     addr = (accessListItem *) malloc(sizeof(accessListItem));
  553.     addr->address = temp;
  554.     addr->numeric = isdigit(*host) ? TRUE : FALSE;
  555.  
  556.     /* For matching, it should not matter whether the list is
  557.        created in the same order as specified or reverse order,
  558.        so each item in the input list will be added to the front. */
  559.  
  560.     addr->next = *list;
  561.  
  562.     *list       =  addr;
  563.  
  564.     return;
  565. }
  566.  
  567.  
  568. /* GU_createAccessList
  569.    create an access list from a string of hostname/IP addresses */
  570.  
  571. accessList
  572. GU_createAccessList(string)
  573. char    *string;
  574. {
  575.     /* pull out host or domain names separated by white space */
  576.  
  577.     char        *cp;
  578.     char        name[256];
  579.     char        *np;
  580.     accessList    list = (accessList) NULL;
  581.  
  582.     cp = string;
  583.     while (*cp != '\0') {
  584.         while (isspace(*cp)  &&  *cp != '\0') { cp++; };
  585.         if ( *cp == '\0' ) break;
  586.  
  587.         np = &(name[0]);
  588.         while (! isspace(*cp)  &&  *cp != '\0') {
  589.             *np = *cp;
  590.             np++;
  591.             cp++;
  592.         }
  593.         *np='\0';
  594.  
  595.         GU_addHostToAccessList(&list, name);
  596.     }
  597.  
  598.     return list;
  599. }
  600.  
  601.  
  602. /* GU_checkAccess
  603.    see if a proposed host access is allowed */
  604.  
  605. BOOLEAN
  606. GU_checkAccess(host, permitList)
  607. char        *host;
  608. accessList    permitList;
  609. {
  610.     accessListItem    *allowed;
  611.     int    hostLen, addrLen;
  612.  
  613.     if (permitList == NULL) return TRUE;
  614.  
  615.     for (allowed=permitList; allowed!=NULL; allowed=allowed->next) {
  616.         hostLen = strlen(host); 
  617.         addrLen = strlen(allowed->address); 
  618.         if (allowed->numeric) {
  619.  
  620.             /* numeric IP address -- compare from start */
  621.  
  622.             if (addrLen <= hostLen) {
  623.             if (strncmp(host, allowed->address, addrLen) == 0) {
  624.                 return TRUE;
  625.             }
  626.  
  627.             }
  628.         } else {
  629.  
  630.             /* character host/domain name -- compare from end */
  631.  
  632.             if (addrLen <= hostLen) {
  633.             if (strcasecmp((host+hostLen-addrLen),
  634.                     allowed->address) == 0) {
  635.                 return TRUE;
  636.             }
  637.             }
  638.         }
  639.     }
  640.  
  641.     return FALSE;
  642. }
  643.  
  644.  
  645. #ifdef DEBUG
  646. void
  647. GU_printAccessList(list)
  648. accessList    list;
  649. {
  650.     accessListItem    *l;
  651.  
  652.     fprintf (stdout, "Host Access List:\n");
  653.  
  654.     for (l=list; l != (accessList) NULL; l=l->next) {
  655.         fprintf (stdout, "\t%s %s\n", l->address,
  656.                 l->numeric ? "(IP address)" : "(host name)");
  657.     }
  658.  
  659.     fprintf (stdout, "    End of Host Access List\n");
  660. }
  661. #endif /* DEBUG */
  662.  
  663.  
  664. /* ====================================================================== */
  665. /*                                                                        */
  666. /* Class methods and common subclass methods of gopher item               */
  667. /*                                                                        */
  668. /* ====================================================================== */
  669.  
  670.  
  671. /* GI_connnectWithStatus
  672.    make a socket connection, posting the status popup and leaving it
  673.    posted for the following processing */
  674.  
  675. int
  676. GI_connectWithStatus(gi)
  677. gopherItemP    gi;
  678. {
  679.     int    s;
  680.  
  681.     showStatus("Connecting to Gopher", STAT_CONNECT,
  682.             gi->host, gi->port);
  683.     s = connectToSocket(gi->host, gi->port);
  684.     if (s < 0) {
  685.  
  686.         (void) removeStatusPanel();
  687.  
  688.         /* could not make a network connection to receive file */
  689.  
  690.         networkError(s, gi->host, gi->port);
  691.         return -1;
  692.     }
  693.  
  694.     /* check for cancel during connection */
  695.  
  696.     if (!updateStatusPanel(1, 0)) {
  697.         (void) removeStatusPanel();
  698.  
  699.         close(s);
  700.         return -1;
  701.     }
  702.  
  703.     return s;
  704. }
  705.  
  706.  
  707. /* GI_getGopherDir
  708.    receive a directory from a gopher server.
  709.    Return the directory pointer for success, NULL for failure. */
  710.  
  711. BOOLEAN
  712. GI_getGopherDir(gfd, d)
  713. int    gfd;
  714. gopherDirP    d;
  715. {
  716.     gopherItemP    gi;
  717.     char        buffer[512];
  718.     int        j, n=0;
  719.     BOOLEAN        goOn;
  720.  
  721.     /* get each item; for each one, decide if it should be kept
  722.        in the directory.  */
  723.  
  724.  
  725.     while ( (gi = GU_getGopherItem(gfd)) != NULL ) {
  726.  
  727.         n++;
  728.  
  729.         if (appResources->showItems == showAll) {
  730.             appendItem(&(d->contents), gi);
  731.         }
  732.             
  733.         else if (appResources->showItems == showKnown  &&
  734.             ! UNKNOWN_TYPE(gi)) {
  735.                 appendItem(&(d->contents), gi);
  736.         }
  737.  
  738.         else if (appResources->showItems == showAccessible  &&
  739.             gi->accessOk) {
  740.                 appendItem(&(d->contents), gi);
  741.         }
  742.  
  743.         else if (appResources->showItems == showAvailable  &&
  744.             ! UNKNOWN_TYPE(gi)  &&  gi->accessOk) {
  745.                 appendItem(&(d->contents), gi);
  746.         }
  747.  
  748.         else {
  749.             freeItem(gi);
  750.             n--;
  751.         } 
  752.  
  753.         if (n % 5 == 0) {
  754.             goOn = updateStatusPanel(n, 0);
  755.             if (!goOn) {
  756.                 freeItemList(&(d->contents));
  757.                 break;
  758.             }
  759.         }
  760.     }
  761.  
  762.     return  (itemListLength(&(d->contents)) != 0);
  763.  
  764.  
  765. /* GI_copyFromNet
  766.    Generic copy setup.  Makes network connection, updates status
  767.    and invokes a type-specific copy procedure. */
  768.  
  769. BOOLEAN
  770. GI_copyFromNet(outFD, gi, specialMsg)
  771. int        outFD;
  772. gopherItemP    gi;
  773. char        *specialMsg;
  774. {
  775.     int    s;
  776.     char    message[MESSAGE_STRING_LEN];
  777.     BOOLEAN rc, goOn = TRUE;
  778.  
  779.     if (gi->sc->copyDataType == copyTypeNone) return;
  780.  
  781.     if (specialMsg == (char *) NULL) {
  782.         sprintf(message, "Copying the %s", gi->sc->typeName);
  783.     } else {
  784.         strcpy(message, specialMsg);
  785.     }
  786.  
  787.     if ((s = GI_connectWithStatus(gi)) < 0) return FALSE;
  788.  
  789.     writeString(s, vStringValue(&(gi->selector)));
  790.     writeString(s, EOL_STRING);
  791.  
  792.     switch (gi->sc->copyDataType) {
  793.  
  794.         case copyTypeAscii:
  795.         rc = GI_copyAsciiFromNet(s, outFD, gi, message);
  796.         break;
  797.     
  798.         case copyTypeBinaryEOF:
  799.         rc = GI_copyBinaryFromNetEOF(s, outFD, gi, message);
  800.         break;
  801.  
  802.         default:
  803.         rc = FALSE;
  804.         break;
  805.     }
  806.  
  807.     close(s);
  808.  
  809.     /* check for cancel */
  810.  
  811.     goOn = removeStatusPanel();
  812.  
  813.     return (rc && goOn);
  814. }
  815.  
  816.  
  817.  
  818. #define MIN_OF(a,b)    ((a)<(b) ? (a) : (b))
  819. #define BIN_BUFFER_SIZE    1400
  820.  
  821. /* GI_copyBinaryFromNetEOF
  822.    Binary copy the contents of one open file descriptor to another.
  823.    Copy until EOF
  824.  
  825.    The file descriptor references on open file for the result.
  826.    For input, we will assume blocking I/O, so a read will always
  827.    return something if it's not yet eof. */
  828.  
  829. BOOLEAN
  830. GI_copyBinaryFromNetEOF(s, outFD, gi, msg)
  831. int        s, outFD;
  832. gopherItemP    gi;
  833. char        *msg;
  834. {
  835.     char    buf[BIN_BUFFER_SIZE];
  836.     int    nr, nw;
  837.     int    byteCt=0;
  838.     BOOLEAN goOn = TRUE;
  839.  
  840.     showStatus(msg, STAT_BINARY,
  841.                 gi->host, gi->port);
  842.  
  843.     while (TRUE) {
  844.         nr = read(s, buf, BIN_BUFFER_SIZE);
  845.  
  846.         if (nr <= 0) {
  847.             if (nr == -1) {
  848.                 perror("GI_copyBinaryFromNetEOF");
  849.                 fprintf (stderr,
  850.                 "Error copying binary data - input.\n");
  851.             }
  852.             break;
  853.         }
  854.  
  855.         byteCt += nr;
  856.         goOn = updateStatusPanel(byteCt, 0);
  857.         if (!goOn) {
  858.             break;
  859.         }
  860.  
  861.         nw = write(outFD, buf, nr);
  862.         if (nw != nr  ||  nw < 0) {
  863.             perror("GI_copyBinaryFromNetEOF");
  864.             fprintf (stderr,
  865.                 "Error copying binary data - output.\n");
  866.             break;
  867.         }
  868.     }
  869.  
  870.     return goOn;
  871. }
  872.  
  873.  
  874. /* GI_copyBinaryFromNetLen
  875.    Binary copy the contents of one open file descriptor to another.
  876.    Copy until a specific number of characters has been copied.
  877.  
  878.    The file descriptor references on open file for the result.
  879.    For input, we will assume blocking I/O, so a read will always
  880.    return something if it's not yet eof. */
  881.  
  882. BOOLEAN
  883. GI_copyBinaryFromNetLen(s, outFD, gi, msg)
  884. int        s, outFD;
  885. gopherItemP    gi;
  886. char        *msg;
  887. {
  888.     char    buf[BIN_BUFFER_SIZE];
  889.     int    nr, nw;
  890.     int    byteCt=0;
  891.     BOOLEAN goOn = TRUE;
  892.     int    nBytes = 0;
  893.  
  894.  
  895.     showStatus(msg, STAT_BINARY,
  896.                 gi->host, gi->port);
  897.  
  898.     while (nBytes > 0) {
  899.         nr = read(s, buf, MIN_OF(nBytes, BIN_BUFFER_SIZE));
  900.  
  901.         if (nr <= 0) {
  902.             if (nr == -1) {
  903.                 perror("GI_copyBinaryFromNetLen");
  904.                 fprintf (stderr,
  905.                 "Error copying binary data - input.\n");
  906.             }
  907.             break;
  908.         }
  909.  
  910.         nBytes -= nr;
  911.  
  912.         byteCt += nr;
  913.         goOn = updateStatusPanel(byteCt, 0);
  914.         if (!goOn) {
  915.             break;
  916.         }
  917.  
  918.         nw = write(outFD, buf, nr);
  919.         if (nw != nr  ||  nw < 0) {
  920.             perror("GI_copyBinaryFromNetLen");
  921.             fprintf (stderr,
  922.                 "Error copying binary data - output.\n");
  923.             break;
  924.         }
  925.     }
  926.  
  927.     return goOn;
  928. }
  929.  
  930.  
  931. /* GI_copyAsciiFromNet
  932.    Copy the contents of one open text file descriptor to another.  Copy
  933.    until a line with a single period in column one or an end of file.
  934.  
  935.    Change "network ASCII" with CR-LF ending each line to standard
  936.    Unix text files with a NL at the end of each line.
  937.  
  938.    Either file descriptor may reference a file or a socket connection. 
  939.    For sockets, we will assume blocking I/O, so a read will always
  940.    return something if it's not yet eof, and a write will write everything. */
  941.  
  942. static char    newLine[1] = {NL};
  943.  
  944. BOOLEAN
  945. GI_copyAsciiFromNet(s, outFD, gi, msg)
  946. int        s, outFD;
  947. gopherItemP    gi;
  948. char        *msg;
  949. {
  950.     char    line[FILE_LINE_LEN];
  951.     int    len;
  952.     int    lineCt=0, wordCt=0;
  953.     BOOLEAN    goOn = TRUE;
  954.  
  955.  
  956.     showStatus(msg, STAT_ASCII,
  957.                 gi->host, gi->port);
  958.  
  959.     if (readLine(s, line, FILE_LINE_LEN) <= 0) {
  960.         (void) removeStatusPanel();
  961.         return FALSE;
  962.     }
  963.     zapCRLF(line);
  964.  
  965.  
  966.     while ( line[0] != '.'  ||  line[1] != '\0') {
  967.  
  968.         lineCt++;
  969.         if ( (lineCt < 50   && lineCt % 10 == 0)  ||
  970.              (lineCt < 500  && lineCt % 50 == 0)  ||
  971.               lineCt % 100 == 0 ) {
  972.             goOn = updateStatusPanel(lineCt, 0);
  973.             if (!goOn) {
  974.                 break;
  975.             }
  976.         }
  977.  
  978.         len = strlen(line);
  979.         write(outFD, line, len);
  980.         write(outFD, newLine, 1);
  981.  
  982.         if (readLine(s, line, FILE_LINE_LEN) <= 0) break;
  983.         zapCRLF(line);
  984.     }
  985.  
  986.     return goOn;
  987. }
  988.  
  989.  
  990.  
  991. #define NET_BUFFER_SIZE    1400
  992.  
  993. /* GI_copyNetUntilEOF
  994.    copy chunks of data from a network file descriptor to a file pointer.
  995.    No termination condition is expected except EOF on the input fd.
  996.    This is a blocking read. */
  997.  
  998. void
  999. GI_copyNetUntilEOF(s, fp)
  1000. int    s;
  1001. FILE    *fp;
  1002. {
  1003.     char    buf[NET_BUFFER_SIZE];
  1004.     int    j;
  1005.  
  1006.     while (TRUE) {
  1007.         j = read(s, buf, NET_BUFFER_SIZE);
  1008.  
  1009.  
  1010.         if (j <= 0) {
  1011.             if (j == -1)
  1012.                 fprintf (stderr,
  1013.                 "Error (%d) copying data from the network\n",
  1014.                     errno);
  1015.             break;
  1016.         }
  1017.         
  1018.         if (fwrite(buf, 1, j, fp) != j) {
  1019.             return;
  1020.         }
  1021.     }
  1022. }
  1023.  
  1024.  
  1025. /* GI_noCopyItem
  1026.    default "can't copy" procedure */
  1027.  
  1028. BOOLEAN
  1029. GI_noCopyItem(gi)
  1030. gopherItemP    gi;
  1031. {
  1032.     int        s;
  1033.     char        *lastName;
  1034.     char        message[MESSAGE_STRING_LEN];
  1035.     
  1036.     sprintf(message, "You cannot copy %s %s to a file.",
  1037.         (GU_isVowel(gi->sc->typeName) ? "an" : "a"), gi->sc->typeName);
  1038.     showError(message);
  1039.  
  1040.     return FALSE;
  1041. }
  1042.  
  1043.  
  1044. /* GI_copyToFile
  1045.    copy a gopher item directly to a file */
  1046.  
  1047. BOOLEAN
  1048. GI_copyToFile(gi)
  1049. gopherItemP    gi;
  1050. {
  1051.  
  1052.     saveNetRequest(gi);
  1053.     return TRUE;
  1054. }
  1055.  
  1056.  
  1057. /* GI_access
  1058.    check the access limits for an item.  This class method will be
  1059.    overridden for many subclasses which need to check the application
  1060.    resources for permission.  */
  1061.  
  1062. BOOLEAN
  1063. GI_access(gi)
  1064. gopherItemP    gi;
  1065. {
  1066.     BOOLEAN    result;
  1067.  
  1068.     result = GU_ftpCheck(gi);
  1069.  
  1070.     return result;
  1071. }
  1072.  
  1073.  
  1074. /* GI_init
  1075.    default class  initialization procedure a subclass of Gopher Item.  This
  1076.    procedure works for class "unknown". */
  1077.  
  1078. void
  1079. GI_init()
  1080. {
  1081.     GU_makePrefix(prefixUnknown,  appResources->prefixUnknown);
  1082.     GU_registerNewType(A_UNKNOWN, &noSubclass);
  1083.  
  1084.     return;
  1085. }
  1086.  
  1087.  
  1088. /* GI_done
  1089.    default class termination procedure a subclass of Gopher Item.  This
  1090.    procedure is a no-op. */
  1091.  
  1092. void
  1093. GI_done()
  1094. {
  1095.     return;
  1096. }
  1097.  
  1098.  
  1099. /* GI_restart
  1100.    default restart cleanup for a subclass of Gopher Item.  This
  1101.    procedure is a no-op. */
  1102.  
  1103. void
  1104. GI_restart()
  1105. {
  1106.     return;
  1107. }
  1108.  
  1109.  
  1110. /* GI_process
  1111.    default processing procedure for a subclass of Gopher Item.  This
  1112.    procedure is a no-op, suggesting an alternative means of processing
  1113.    the item.  Every known item type should override this procedure. */
  1114.  
  1115. BOOLEAN
  1116. GI_process(gi)
  1117. gopherItemP     gi;
  1118. {
  1119.     char message[MESSAGE_STRING_LEN];
  1120.  
  1121.     sprintf(message,
  1122.         "There is no automatic processing for this gopher item type (%c)",
  1123.         gi->type);
  1124.     strcat(message,
  1125.         "\nYou may be able to use the Copy command to access the file.");
  1126.     showError(message);
  1127.  
  1128.     return FALSE;
  1129. }
  1130.